#!/usr/bin/python
# -*- coding: utf-8 -*-

u"""
This module provides HandlerInterface class
"""

__all__ = [u"HandlerInterface"]
__date__ = "27 mai 2010"
__author__ = "guillaume"

import os
import sys
import gettext

from _pylot import TRANSLATORS
from _pylot.core.config import Configuration
from _pylot.core.impl_stdterminal import (IOHandler, ProcessHandler,
                                           DEFAULT_IO_HANDLER,
                                           DEFAULT_PROCESS_HANDLER)
from _pylot.core.book import Book
from _pylot.core.collection import Collection
from _pylot.core.datum import Datum
from _pylot.core.path import Path
from _pylot.core.pathbook import PathBook
from _pylot.core.plugin import Plugin
from _pylot.core.system import LEVELS, LANG, ENCODING
from _pylot.core.utils import (check_type, good_level, uprint, find_file,
                                error, make_aliases)

from _pylot.data import regexpr
from _pylot.data.regexpr import match

from _pylot.interfaces.iohandler import IOHandlerInterface
from _pylot.interfaces.processhandler import ProcessHandlerInterface
from _pylot.defaultconf import hardbook
from _pylot import install_translator

def set_pylot_texts(obj_text_list, field=u'text'):
  if field == u'text':
    for obj, text in obj_text_list :
      if isinstance(obj, Datum) :
        obj.alias = text
      elif isinstance(obj, (Collection, Book)):
        obj.identity.alias = text
      else :
        pass
  elif field == u'tooltip':
    for obj, text in obj_text_list :
      if isinstance(obj, Datum) :
        obj.tooltip = text
      elif isinstance(obj, (Collection, Book)):
        obj.identity.tooltip = text
      else :
        pass

class HandlerInterface(object):
  u"""
  HandlerInterface provides a common interface for core part of the application.
  An handler is class which "handles" data, make computations, handles I/O, ...
  """
  def __init__(self, plugin_path=None, plugin_id=None):
    u"""
    :param plugin_path: plugin's path : ex /home/guillaume/plugins/sample
    :param plugin_id: plugin's identifier. If not defined, generate it from plugin's path (sample in this case)
    """
    self.guiflag = False
    self.__translators = {}
    self.__current_locale = None
    self._userconf = Path(u'userconf').abspath()

    self.script_globals = {u'manager':self}
    self.script_pathbook = PathBook()
    self.script_pathbook.identity = hardbook.identity

    self._defaultconf = []
    # add default config directory required by pylot
    try :
      # if pylot is installed
      from _pylot.defaultconf import defaultconf as dc
    except ImportError :
      # we must be in root directory
      self._defaultconf.append(Path(u'pylot', u'etc').abspath())
    else :
      self._defaultconf.append(dc)

    if plugin_path :
      plugin_path = Path(plugin_path)
      self._plugin_path = plugin_path
      name = self._plugin_path.name
      self._defaultconf.append(self._plugin_path)
      mo_file_dir = Path(self._plugin_path, u'i18n')
      name = self._plugin_path.name.encode(ENCODING)
      if mo_file_dir.isdir() :
        for path in mo_file_dir.listdir() :
          if path.isdir() and match(regexpr.co_i18n_locale, str(path.name)) :
            locale = path.name
            try :
              self.__translators[locale] = gettext.translation(name,
                mo_file_dir.encode(ENCODING),
                languages=[locale.encode(ENCODING)])
            except IOError: pass


    else :
      self.__translators = TRANSLATORS
      name = u'unnamed'

    if plugin_id is None :
      self.name = name
    else :
      self.name = plugin_id

    self.alias = self.name
    self.tooltip = u'No tooltip, please set "handler.tooltip" attribute'

    self._options = {} # dict key: collection name, value: colletion object
    self._user_options = []# user option (collections or book) to save/read

    self._children = {}

    # current level
    self._level = u'user'
    self._process = None
    self._io = None

  def _tr(self, txt):
    u"""
    Private method only used if i18n fails. 
    Please NEVER use this method.
    You MUST use :meth:`~_pylot.interfaces.handler.tr_`
    """
    return txt

  def tr_ (self, txt):
    u"""
    Use this method to translate strings specific to plugin.
    exemple:
    self.tr_(u'My Plugin description')
    
    This method is similar to default gettext "_" method.
    the parameter must be an **unicode** string. The translation is also an
    unicode string. If no translations available, original unicode string is 
    returned.
    
    This method is also recognised by plugin_editor and i18n tools, so all
    these string are extracted and store in plugin i18n/msg.pot file

    .. seealso ::

      Program used for translation : `Babel <http://babel.edgewall.org/>`_
      Original builtin i18n library : `Python gettext <http://docs.python.org/library/gettext.html>`_
      Official `Unicode Howto <http://docs.python.org/howto/unicode.html>`_

    """
    return self._tr(txt)

  def _setlevel(self, level):
    u"""
    :param level: interface level. If level isn't available, expert level is used
    :type level: unicode
    """
    check_type(u'level', level, unicode)
    if level in LEVELS :
      self._level = level
    else :
      uprint(tr_(u'Unknown level %s, level set to "expert"\n') % level,
        file=sys.stderr)
      self._level = u'expert'

  def _setio(self, io_handler):
    u"""
    :param io_handler: The IOHandler you want to use
    :type io_handler: :class:`~_pylot.interfaces.iohandler.IOHandlerInterface`
    """
    check_type(u'io_handler', io_handler, IOHandlerInterface)
    self._io = io_handler

  def _setprocess(self, process_handler):
    u"""
    :param process_handler: The ProcessHandler you want to use
    :type process_handler: :class:`~_pylot.interfaces.processhandler.ProcessHandlerInterface`
    """
    check_type(u'process_handler', process_handler, ProcessHandlerInterface)
    self._process = process_handler

  def _setuserconf(self, userconf):
    self._userconf = userconf

  level = property(fget=lambda self:self._level, fset=_setlevel)
  io = property(fget=lambda self:self._io, fset=_setio)
  process = property(fget=lambda self:self._process, fset=_setprocess)
  plugin_path = property(fget=lambda self:self._plugin_path)
  defaultconf = property(fget=lambda self:self._defaultconf)
  userconf = property(fget=lambda self:self._userconf, fset=_setuserconf)

  def findFile(self, filename):
    u"""
    Searches file "filename" in plugin directory and default directories.
    filename can be a simple filename (ex: u'sample.conf') or a relative
    path (ex: Path(u'subdir', u'sample.conf'))
    
    :type filename: obj:`unicode` or :class:`~_pylot.core.path.Path`
    :return: absolute path or None
    :rtype: obj:`None` or :class:`~_pylot.core.path.Path`
    """
    return find_file(filename, self._defaultconf)

  def goodLevel(self, option_level):
    u"""
    goodLevel says if option_level can be displayed for this handler.
    
    For example, if current handler level is devel (an intermediate level) :
      - goodLevel(u'expert') returns False
      - goodLevel(u'user') returns True
    
    Available levels are (highest to lowest):
      - u'expert': all options, commands, widgets, etc.
      - u'devel': core developer options and lowest
      - u'devel': plugin developer, curious users
      - u'user': standard user
      
    This method gives information about "user interface level" not about 
    "verbosity". This difference is very important. For example core developer
    could want to access to all commands available in library (u'expert') but
    maybe without command outputs (u'silent') or with standard messages (u'std')
    
    For verbosity levels, please have a look to 
    :class:`~_pylot.interfaces.iohandler.IOHandlerInterface`.
    
    :param option_level: The level you want to test
    :type option_level: :obj:`unicode`
    """
    level_dict = {u'hidden':5, u'expert':4, u'user':1, u'devel':2}
    return good_level(option_level, level_dict, self.level)

  def switchUserLevel(self, level=u'user'):
    u"""
    Switch dynamically the level of User Interface.
    
    .. seealso ::
    
      HandlerInterface Method 
      :meth:`~_pylot.interfaces.handler.HandlerInterface.goodLevel`
      
    :param level: The level you want to reach
    :type level: :obj:`unicode`
    """
    self.level = level
    for module in self._children.values() :
      module.switchUserLevel(self.level)

  def setUserconf(self, userconf):
    for child in self._children.values() :
      child.setUserconf(userconf)
    self.userconf = userconf

  def getPlugin(self, identifier):
    u"""
    Returns plugin corresponding to identifier. If not exists, returns None.
    If exists, the Plugin is always type of HandlerInterface or derivated class.
    
    .. seealso ::
      :meth:`~_pylot.interfaces.handler.HandlerInterface.addPlugin`

    :param identifier: Plugin identifier
    :type identifier: :obj:`unicode` or :obj:`None`
    """
    try :
      return self._children[identifier]
    except KeyError :
      return None

  def getCurrentLocale(self):
    return self.__current_locale

  def init(self, io_handler=None, process_handler=None):
    u"""
    Initialise handler's io_handler and process_handler.
    If arguments io_handler or process_handler not set (=None), default handlers
    are used (see default implementation).
    
    :param io_handler: Instance of an IOHandlerInterface implementation
    :type io_handler: :class:`pylot.interfaces.IOHandlerInterface`
    :param process_handler: Instance of a ProcessHandlerInterface implementation
    :type process_handler: :class:`pylot.interfaces.ProcessHandlerInterface`
    :return: self (allow to write handler = HandlerInterface().init(...) )
    :rtypes: :class:`~_pylot.interfaces.iohandler.IOHandlerInterface`
    """
    if io_handler :
      self.io = io_handler
    if process_handler :
      self.process = process_handler
    if self._process is None :
      self.process = DEFAULT_PROCESS_HANDLER
    if self._io is None :
      self.io = DEFAULT_IO_HANDLER
    if self._level is None :
      self.level = u'user'

    for module in self._children.values() :
      module.io = self.io
      module.process = self.process
      module.init()

    return self

  def addPlugin(self, name, plugin_id=None, aliases=[]):
    u"""
    Adds the handler part of plugin 'name' to current handler.
    This method creates automatically an instance of plugin.
    This instance becomes a child of current handler.

    If you want to add two times (or more) a plugin, you must give different
    plugin_id(s), else an error is raised.
    
    Example :
    handler.addPlugin(u'sample', u'sample_1')
    handler.addPlugin(u'sample', u'sample_2')

    If plugin_id is not defined, "plugin_id" is "name"

    :param name: plugin's name, not path
    :type name: :obj:`unicode`
    :param plugin_id: optionnal identifier
    :type plugin_id: :obj:`unicode`
    """
    mod = Plugin(name, self.guiflag)
    handler = mod.handler_class(plugin_id=plugin_id)
    plugin_id = handler.name
    make_aliases(handler, self, aliases)
    return self.addpyLotHandler(handler, plugin_id)

  def _setTexts(self, obj_text_list, field=u'text'):
    u"""
    available fields :
      - text : datum, book, collection aliases
      - tooltip : datum, book, collection tooltips
    """
    if field not in (u'text', u'tooltip') :
      self.io.writeStderr(tr_(u'Unknown field "%(FIELD)s"') % dict(FIELD=field))
    set_pylot_texts(obj_text_list, field)

  def addpyLotHandler(self, handler, handler_id=None):
    if handler_id is None :
      handler_id = handler.name
    if handler_id in self._children :
      raise NameError, error(tr_(u'A plugin named "%s" yet exists, please change "plugin_id" argument') % plugin_id)
    else :
      self._children[handler_id] = handler

    return handler

  def addOptions(self, filename, main=False, user=True):
    u"""
    Reads a csv :class:`~pylot.core.Configuration` file (filename) and adds 
    it to handler's options.

    :param filename: csv Collection file.
    :param user: if True, it saves configuration in userconf directory. See (read|write)Settings
    :type user: :obj:`bool`
    :param main: if main is True, option can be reached directly via "opt" attribute.
    
    :return: corresponding Configuration
    :rtype: :class:`~pylot.core.Configuration`w
    """
    obj = Configuration()
    csv_file = self.findFile(filename)
    if csv_file :
#      self.io.writeStdout(tr_(u'Load configuration %s\n') % csv_file,
#                          level=u'verbose')
      obj.upFromFile(csv_file, dataformat=u'csv')

    if obj.name is None :
      raise NameError, error(tr_(u'%s must be named') % obj.__name__)
    else :
      setattr(self, u'_opt_%s' % obj.name, obj)
      if main is True :
        setattr(self, u'opt', obj)
      self._options[obj.name] = obj
      if obj not in self._user_options and user is True:
        self._user_options.append(obj)

    return obj

  def hasUserOptions(self):
    u"""
    Returns True if handler has User Options.
    User options are options added with 
    :meth:`~_pylot.interfaces.handler.HandlerInterface.addOptions`
    and user=True
    :rtype: :obj:`bool`
    """
    return bool(len(self._user_options))

  def translate(self, locale=None, force=False, recursive=True):
    u"""
    Translates dynamically the handler.
    Locale is an unicode string composed of two parts separated by '_'.
    The first part is the lang, the second the country.
    
    For example, British English is defined by u'en_GB'.
    French from France: u'fr_FR'

    .. seealso ::
      
      pylot howto for i18n
  
    :param locale: the new locale (ex: u'fr_FR')
    :type locale: :obj:`unicode`
    """
    # install translator local to plugin (can be accessed using tr_ method)
    if locale is None :
      locale = LANG
    if locale == self.__current_locale and force is False:
      return
    self.__current_locale = locale

    # install global translator in builtin namespace 
    #(can be accessed using _ function)
    install_translator(locale)

    try :
      self._tr = self.__translators[locale].ugettext
    except KeyError :
      pass

    if recursive is True :
      for child in self._children.values():
        child.translate(locale, force, recursive)

    # retranslate handler
    self._translate()

  def readSettings(self, settings, common=None):
    u"""
    Reads settings recursively (ie call children readSettings)

    :param settings: settings you want to read
    :type settings: :class:`~pylot.core.Settings`
    """
    if common is None :
      common = settings
    for child in self._children.values() :
      child.readSettings(settings, common)
    self._readSettings(settings, common)

  def writeSettings(self, settings, common=None):
    u"""
    Writes settings recursively (ie call children writeSettings)

    :param settings: where you want to save settings
    :type settings: :class:`~pylot.core.Settings`
    """
    if common is None :
      common = settings
    for child in self._children.values() :
      child.writeSettings(settings, common)
    self._writeSettings(settings, common)

  def quit(self):
    for child in self._children.values() :
      child.quit()
    self._quit()

  def _translate(self):
    pass

  def _readSettings(self, settings, common):
    pass

  def _writeSettings(self, settings, common):
    pass

  def _quit(self):
    pass

DEFAULT_HANDLER = HandlerInterface(hardbook(u'pythonpath'))
DEFAULT_HANDLER.init(DEFAULT_IO_HANDLER, DEFAULT_PROCESS_HANDLER)
